package com.yeskery.nut.scan.advice;

import com.yeskery.nut.core.*;
import com.yeskery.nut.plugin.BindContext;
import com.yeskery.nut.plugin.InterceptorPlugin;
import com.yeskery.nut.util.ReflectUtils;
import com.yeskery.nut.util.StringUtils;
import com.yeskery.nut.view.ViewHandler;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * ModelAttribute注解拦截器
 * @author sprout
 * 2022-06-16 18:10
 */
public class ModelAttributePlugin extends BaseControllerAdviceAnnotationHandler implements InterceptorPlugin {

    /** 属性key，元数据map */
    private final Map<ControllerAdviceCombinationMetadataAttributes, String> metadataAttributeMap = new HashMap<>();

    /** 包默认理器 */
    protected final Map<String, List<ControllerAdviceCombinationMetadataAttributes>> packageDefaultMetadataMap = new HashMap<>();

    @Override
    public boolean beforeHandle(Request request, Response response, Execution execution, Controller controller) throws Exception {
        String controllerPackageName = getControllerPackageName(controller);
        if (packageDefaultMetadataMap.keySet().stream()
                .noneMatch(k -> DEFAULT_EMPTY_PACKAGES.equals(k) || controllerPackageName.startsWith(k))) {
            return true;
        }

        List<ControllerAdviceCombinationMetadataAttributes> validMetadataAttributes = new LinkedList<>();
        for (Map.Entry<String, List<ControllerAdviceCombinationMetadataAttributes>> entry : packageDefaultMetadataMap.entrySet()) {
            String packageName = entry.getKey();
            if (DEFAULT_EMPTY_PACKAGES.equals(packageName) || controllerPackageName.startsWith(packageName)) {
                validMetadataAttributes.addAll(entry.getValue());
            }
        }

        for (ControllerAdviceCombinationMetadataAttributes validMetadataAttribute : validMetadataAttributes) {
            String attributeName = metadataAttributeMap.get(validMetadataAttribute);
            Method method = validMetadataAttribute.getMethod();
            Parameter[] parameters = method.getParameters();
            Object[] params = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                Class<?> type = parameters[i].getType();
                if (type.isAssignableFrom(Request.class)) {
                    params[i] = request;
                } else if (type.isAssignableFrom(Session.class)) {
                    params[i] = request.getSession();
                } else if (type.isAssignableFrom(Response.class)) {
                    params[i] = response;
                } else if (type.isAssignableFrom(Execution.class)) {
                    params[i] = execution;
                }  else if (type.isAssignableFrom(Forward.class)) {
                    params[i] = execution.getForward();
                } else if (type.isAssignableFrom(BindContext.class)) {
                    params[i] = execution.getBindContext();
                } else if (type.isAssignableFrom(ViewHandler.class)) {
                    params[i] = execution.getViewHandler();
                } else if (type.isAssignableFrom(Model.class)) {
                    params[i] = Model.DEFAULT_MODEL;
                }
            }

            Class<?> type = validMetadataAttribute.getType();
            Object target = ReflectUtils.getTargetByCache(getApplicationContext(), targetMap, type);

            try {
                Object result = method.invoke(target, params);
                if (result != null) {
                    if (Map.class.isAssignableFrom(result.getClass())) {
                        for (Map.Entry<?, ?> entry : ((Map<?, ?>) result).entrySet()) {
                            RequestApplicationContext.putResource(entry.getKey().toString(), entry.getValue());
                        }
                    } else if (Collection.class.isAssignableFrom(result.getClass())) {
                        for (Object object : ((Collection<?>) result)) {
                            RequestApplicationContext.putResource(object);
                        }
                    } else {
                        RequestApplicationContext.putResource(StringUtils.isEmpty(attributeName)
                                ? result.toString() : attributeName, result);
                    }
                }
            } catch (Exception e1) {
                throw new ProxyObjectInvokeException("Target Method Execute Fail.", e1);
            }
        }
        return true;
    }

    /**
     * 注册全局绑定属性
     * @param key 属性Key
     * @param basePackages 扫描基础包
     * @param metadataAttribute 元数据
     */
    public void registerModelAttribute(String key, String[] basePackages, ControllerAdviceCombinationMetadataAttributes metadataAttribute) {
        if (basePackages == null || basePackages.length == 0) {
            List<ControllerAdviceCombinationMetadataAttributes> defaultMetadataList = packageDefaultMetadataMap.get(DEFAULT_EMPTY_PACKAGES);
            if (defaultMetadataList == null) {
                defaultMetadataList = new ArrayList<>();
            }
            defaultMetadataList.add(metadataAttribute);
            packageDefaultMetadataMap.put(DEFAULT_EMPTY_PACKAGES, defaultMetadataList);
        } else {
            for (String basePackage : basePackages) {
                List<ControllerAdviceCombinationMetadataAttributes> defaultMetadataList = packageDefaultMetadataMap.get(basePackage);
                if (defaultMetadataList == null) {
                    defaultMetadataList = new ArrayList<>();
                }
                defaultMetadataList.add(metadataAttribute);
                packageDefaultMetadataMap.put(basePackage, defaultMetadataList);
            }
        }
        metadataAttributeMap.put(metadataAttribute, key);
    }
}
